package com.huluspace.nosqlredis.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.huluspace.nosqlredis.model.Role;
import com.huluspace.nosqlredis.model.User;
import com.huluspace.nosqlredis.repository.UserRepository;
import com.huluspace.nosqlredis.security.CustomUserDetails;
import com.huluspace.nosqlredis.security.JwtUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class JwtAuthService {
  private static final String prefix = "user:roles:"; // key的前缀

  final private JwtUtil jwtUtil;
  final private UserRepository userRepository;
  final private StringRedisTemplate stringRedisTemplate;
  final private ObjectMapper objectMapper;
  public JwtAuthService(JwtUtil jwtUtil, UserRepository userRepository, StringRedisTemplate stringRedisTemplate, ObjectMapper objectMapper) {
    this.jwtUtil = jwtUtil;
    this.userRepository = userRepository;
    this.stringRedisTemplate = stringRedisTemplate;
    this.objectMapper = objectMapper;
  }

  // 判断token是否无效
  public boolean isTokenInvalid(String token) {
    return !jwtUtil.validToken(token) || stringRedisTemplate.hasKey("blacklist:" + token);
  }

  // 基于JWT鉴权并封装用户详情
  public CustomUserDetails loadUserByToken(String token) {
    // 检查token是否无效或否已注销
    if (isTokenInvalid(token)) {
      throw new RuntimeException("令牌无效或已注销");
    }
    String username = jwtUtil.extractUserName(token);

    // 尝试从缓存获取权限
    List<String> roles = getCachedRoles(username);
    if (!roles.isEmpty()) {
      // 用户权限存在缓存，说明用户存在，避免DB查询
      List<GrantedAuthority> authorities = roles.stream()
          .map(SimpleGrantedAuthority::new)
          .collect(Collectors.toList());
      return new CustomUserDetails(null, username, "", authorities); // userId 可为空或另行缓存
    }

    // 缓存缺失：访问数据库确认用户存在性，并尝试获取权限
    User user = userRepository.findByUsernameWithRoles(username)
        .orElseThrow(() -> new UsernameNotFoundException("不存在用户：" + username));
    List<String> fetchedRoles = user.getRoles().stream()
        .map(Role::getName)
        .toList();
    // 写入缓存
    cacheRoles(username, fetchedRoles);
    List<GrantedAuthority> authorities = fetchedRoles.stream()
        .map(SimpleGrantedAuthority::new)
        .collect(Collectors.toList());
    return new CustomUserDetails(user.getId(), username, "", authorities);
  }

  // 事务内访问懒加载属性
  private List<String> findRolesByUsername(String username) {
    return userRepository.findByUsernameWithRoles(username)
        .map(user -> user.getRoles().stream()
            .map(Role::getName)
            .toList())
        .orElse(List.of());
  }

  private void cacheRoles(String username, List<String> roles)  {
    String json = null;
    try {
      json = objectMapper.writeValueAsString(roles);
    } catch (JsonProcessingException e) {
      throw new RuntimeException("将用户权限列表序列化为JSON失败，username=" + username + ", roles=" + roles, e);
    }
    stringRedisTemplate.opsForValue().set(prefix + username, json, 1, TimeUnit.HOURS);
  }

  private List<String> getCachedRoles(String username) {
    String json = stringRedisTemplate.opsForValue().get(prefix + username);
    if (json == null) {
      return Collections.emptyList();
    }
    try {
      return objectMapper.readValue(json, new TypeReference<List<String>>() {});
    } catch (JsonProcessingException e) {
      throw new RuntimeException("反序列化Redis中"+prefix + username + "的缓存失败，内容为：" + json, e);
    }
  }

  // 清除权限缓存
  public void clearUserAuthorityCache(String username) {
    stringRedisTemplate.delete(prefix + username);
  }
  // 清除令牌
  public void invalidateToken(String token) {
    stringRedisTemplate.opsForValue().set("blacklist:" + token, "1", 10, TimeUnit.MINUTES);
  }
}
